package enron;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Set;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPANames;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;
import enron.Generator.Generator;

public class VendorAgent extends Agent {
	private static final long serialVersionUID = -4757243184712195097L;
	public static final NumberFormat energyFormat = new DecimalFormat("0.000");
	
	private Set<AID> buyerList = new HashSet<AID>();
	private Long power = 0L;
	private double price = 1.0;
	private double energyGenerated = 0.0;
	private EnergyGrid myGrid;

	@SuppressWarnings("unused")
	private class LocationType { 
		public double longitude;
		public double latitude;
	}

	private LocationType location = new LocationType();
	private ArrayList<Generator> generators;
	private VendorAgentUI window;
	protected Long lastUpdate;
	
	/* (non-Javadoc)
	 * @see jade.core.Agent#setup()
	 */
	@Override
	protected void setup() {
		Object[] args = getArguments();
		lastUpdate = System.currentTimeMillis();
		if(args != null && args.length > 0) {
			location.longitude = (Double) args[0];
			location.latitude = (Double) args[1];
			
			myGrid = (EnergyGrid) args[2];
			
			setGenerators(args[3]);
		} else {
			System.err.println("Vendor agent recieved no arguments.");
			doDelete();
		}
		registerWithDirectoryService();
		window = new VendorAgentUI(this);

		addSellEnergyBehaviour();
		addAdjustGeneratorBehaviour();
		
		addBehaviour(new TickerBehaviour(this,250) {
			private static final long serialVersionUID = -6972736261803317042L;
			@Override
			protected void onTick() {
				updateEnergy();
			}
		});
		super.setup();
	}
	
	private void addAdjustGeneratorBehaviour() {
		MessageTemplate template = MessageTemplate.and(
				MessageTemplate.MatchProtocol(FIPANames.InteractionProtocol.FIPA_CONTRACT_NET),
				MessageTemplate.MatchPerformative(ACLMessage.CFP) );
		addBehaviour(new AdjustGeneratorBehaviour(this,template));
	}

	private void addSellEnergyBehaviour() {
		MessageTemplate template = MessageTemplate.and(
				MessageTemplate.MatchProtocol(FIPANames.InteractionProtocol.FIPA_ITERATED_CONTRACT_NET),
				MessageTemplate.MatchPerformative(ACLMessage.CFP) );
		addBehaviour(new SellEnergyDispatcher(this,template));
	}
	
	protected void registerWithDirectoryService() {
		DFAgentDescription dfd = new DFAgentDescription();
		dfd.setName(getAID()); 
		ServiceDescription sd = new ServiceDescription();
	    sd.setType("energy-vendor");
	    sd.setName(getAID().getName());
	    dfd.addServices(sd);
	    try { 
	      DFService.register(this, dfd); 
	    }
	    catch (FIPAException fe) { 
	      fe.printStackTrace(); 
	    }
	}
	
	public Set<AID> getBuyerList() {
		return buyerList;
	}
	public void setBuyerList(Set<AID> buyerList) {
		this.buyerList = buyerList;
	}
	public double getPrice() {
		return price;
	}
	public void setPrice(double price) {
		this.price = price;
	}
	public Long getPower() {
		return power;
	}
	public void setPower(Long power) {
		this.power = power;
	}

	public void changePower(Long changeAmount) {
//		System.out.print("changing... ");
		updateEnergy();
		myGrid.adjustSupply(changeAmount);
		power += changeAmount;
		window.getLblWattageValue().setText(power/1000.0 + "kW");
	}

	public double getEnergyGenerated() {
		return energyGenerated;
	}
	
	public void sellEnergy(Double energySold) {
		energyGenerated -= energySold;
	}

	public void setEnergyGenerated(Double energyGenerated) {
		this.energyGenerated = energyGenerated;
	}

	/**
	 * @return the generators
	 */
	public ArrayList<Generator> getGenerators() {
		return generators;
	}


	public void setGenerators(ArrayList<Generator> generators) {
		this.generators = generators;
		for(Generator currentGenerator : this.generators) {
			currentGenerator.setAgent(this);
			this.power += currentGenerator.getCurrentSupply();
		}
	}
	
	@SuppressWarnings("unchecked")
	private void setGenerators(Object generators) {
		for(Generator gen : (ArrayList<Generator>)generators) {
			gen.setAgent(this);
			myGrid.adjustSupply(gen.getCurrentSupply());
		}
		setGenerators((ArrayList<Generator>)generators);
	}
	
	public Generator[][] getGeneratorArray() {
		Generator[][] returnVal = new Generator[generators.size()][1];
		for(int i = 0; i < generators.size(); i++) {
			returnVal[i][0] = generators.get(i);
		}
		return returnVal;
	}

	/* (non-Javadoc)
	 * @see jade.core.Agent#takeDown()
	 */
	@Override
	protected void takeDown() {
		try {
			DFService.deregister(this);
		} catch (FIPAException e) {
			e.printStackTrace();
		}
		super.takeDown();
	}
	
	private void updateEnergy() {
		Long currentTime = System.currentTimeMillis();
		double newEnergy = ((double)(currentTime-lastUpdate)/3600000.0) * ((double)power/1000.0);
		//myGrid.addEnergy(newEnergy);
		energyGenerated += newEnergy;
		lastUpdate = currentTime;
		window.getLblEnergyValue().setText(energyFormat.format(energyGenerated) + "kWh");
	}
	
	public boolean adjustGenerators(Long changeAmount) {
		int loopLimit = 10;
		@SuppressWarnings("unchecked")
		ArrayList<Generator> generators = (ArrayList<Generator>) this.generators.clone();
		boolean adjusted = false;
		while(!adjusted && (changeAmount > 0L || power > 0L) && loopLimit > 0) {
			loopLimit--;
			Collections.sort(generators,  new Comparator<Generator>(){
				
				@Override
				public int compare(Generator gen1, Generator gen2) {
					return Long.valueOf((gen2.getMaximumSupply() - gen2.getCurrentSupply()) - (gen1.getMaximumSupply() - gen1.getCurrentSupply())).intValue();
				}
			});
			
			for(Generator generator : generators) {
//				System.out.print(loopLimit + " Trying to adjust " + generator.getDisplay() + " ");
				if(generator.adjustSupplyBy(changeAmount)) {
//					System.out.println("  Done!");
					adjusted = true;
					break;
				}
//				System.out.println();
			}
			
			if(!adjusted) {
//				System.out.println("Change amount = " + changeAmount);
				if(changeAmount < 0) {
					Collections.sort(generators,  new Comparator<Generator>(){

						@Override
						public int compare(Generator gen1, Generator gen2) {
							return Long.valueOf((gen1.getMaximumSupply() - gen1.getCurrentSupply()) - (gen2.getMaximumSupply() - gen2.getCurrentSupply())).intValue();
						}
					});
					for(Generator generator : generators) {
						if(generator.getCurrentSupply() != 0L){
//							System.out.print("Trying to turn off " + generator.getDisplay() + " ");
							changeAmount += generator.getCurrentSupply();
							generator.turnOff();
//							System.out.println(this.getLocalName() + " - Remainder: " + changeAmount);
							if(changeAmount == 0L) {
//								System.out.print("  Done!");
								adjusted = true;
							}
//							System.out.println();
							break;
						}
					}
				} else {
					for(Generator generator : generators) {
						if(generator.getCurrentSupply() != generator.getMaximumSupply()){
//							System.out.print("Trying to max out " + generator.getDisplay() + " ");
							changeAmount -= generator.getCurrentSupply();
							generator.adjustSupplyTo(generator.getMaximumSupply());
//							System.out.println(this.getLocalName() + " - Remainder: " + changeAmount);
							if(changeAmount == 0L) {
//								System.out.print("  Done!");
								adjusted = true;
							}
//							System.out.println();
							break;
						}
					}
				}
			}
		}
		return adjusted;
	}
	
	Double getAdjustmentCost(Long changeAmount) {
		@SuppressWarnings("unchecked")
		ArrayList<Generator> generators = (ArrayList<Generator>) this.generators.clone();
		class GenCompare implements Comparator<Generator> {
			Long changeAmount;
			GenCompare(Long changeAmount){
				super();
				this.changeAmount = changeAmount;
			}
			
			@Override
			public int compare(Generator gen1, Generator gen2) {
				return Double.compare(gen2.getTurnDownCost(changeAmount), gen1.getTurnDownCost(changeAmount));
			}
		}
		Collections.sort(generators, new GenCompare(changeAmount));
		return generators.get(0).getTurnDownCost(changeAmount);
	}

	public EnergyGrid getGrid() {
		return myGrid;
	}
}
